<?php
require_once("drivers/mysql.php");
interface Model {
    const TYPE_INT = 1;
    const TYPE_VARCHAR = 2;
    const TYPE_DATE = 3;
    const TYPE_JSON = 4;
    const TYPE_DATETIME = 5;
    const TYPE_DATE_ADDITION = 6;
    const TYPE_TEXT = 7;

    const MOD_PRIMARY = 1;
    const MOD_MD5     = 2;
    const MOD_ACCEPT  = 4;
    const MOD_ACCEPTDATE = 8;
    const MOD_FOREIGN = 16;
    const MOD_REMOTE  = 32;
    const MOD_CUMULATED = 64;
    const MOD_UPDATE = 128;

    function save($id, $data);
    function load($id);
    function delete($id);
    function loadAll($condition);
    function getIdField();
    function getCount($condition);
    function isAuthorized($operation, $subject = "");
    function getSince($date);
    function accept($id);
    function reject($id);
    function validate($params);
}

interface Formatter {
    function generateResult();
}

class Database {
    private static $dbDescriptor = "";
    private static $current = "";
    private static $model;
    private static $database = "";
    private static $driver = "";
    private static $driverObject = null;
    private static $lastInsertedId;

    static function setConnection($host, $user, $pw, $dbname, $driver) {
        $driverClass = "driver_" . $driver;
        self::$database = $dbname;
        self::$driver = $driver;
        self::$driverObject = new $driverClass($host, $user, $pw);
        self::$driverObject->selectDatabase($dbname);
    }

    static function selectConnection($id) {
//        mysql_select_db(self::$databases[$id], self::$dbDescriptors[$id]);
    }

    static function calculateLimit($pageno, $recno) {
        $start = ($pageno-1) * $recno;
        return array(
            $start,
            $recno
        );
    }

    static function loadModel($modelName) {
        $modelFile = "site/models/$modelName.php";
        if (file_exists($modelFile)) {
            require_once($modelFile);
        } else {
            throw new Exception("Model file not found: " . $modelFile);
        }
        $modelClass = $modelName . "Model";
        self::$model = new $modelClass();
        if (!self::$model instanceof Model) { throw new Exception("Invalid model type"); }
    }

    static function getAll($condition = "true", $fields = array(), $formatterClass = "", $preview = false) {
        if (self::$model->isAuthorized("list")) {
            self::$model->setProjection($fields);
            $result = self::$model->loadAll($condition, $preview);
            if ($formatterClass == "") {
                return $result;
            } else {
                $cl = new $formatterClass($result);
                if (!$cl instanceof Formatter) { throw new Exception("Invalid formatter type"); }
                return $cl->generateResult();
            }
        }
        return null;
    }

    static function validate($params) {
        return self::$model->validate($params);
    }

    static function accept($id) {
        self::$model->accept($id);
    }
    
    static function reject($id) {
        self::$model->reject($id);
    }

    static function getSince($date, $condition = "") {
        return self::$model->getSince($date, $condition);
    }

    static function getItem($id, $fromUpdate = false) {
        return self::$model->load($id, $fromUpdate);
    }

    static function getModel() {
        return self::$model;
    }

    static function getAllCount($condition = "true") {
        return self::$model->getCount($condition);
    }

    static function addNew($data) {
        $result = 0;
        if (self::$model->isAuthorized("addnew")) {
//            $data = self::$model->beforeSave($data);
            $result = self::$model->save(null, $data);
            if ($result == "") { $result = self::$lastInsertedId; }
            self::$model->afterSave($data);
        }
        return $result;
    }

    static function getSimpleField($res) {
        $rec = self::getDataFromresource($res);
        $fldArray = array_keys($rec[0]);
        return $rec[0][$fldArray[0]];
    }

    static function getDataFromResource($res) {
        $result = array();
        while ($rec = mysql_fetch_assoc($res)) {
            $result[] = $rec;
        }
        return $result;
    }

    static function prepareData($data, $id = "", $quoteExcepts = array()) {
        foreach ($data as $key=>$value) {
            if (in_array($key, $quoteExcepts)) {
                $result[$key] = $value;
            } else {
                $result[$key] = self::$dbDescriptor->escape($value);
            }
        }
        if ($id != "") { $result[self::$model->getIdField()] = $id; }
        return $result;
    }

    static function update($data, $quote = false) {
        if (self::$model->isAuthorized("update")) {
            $data = self::$model->beforeSave($data);
            if ($quote) {
                $data2 = $data;
                foreach ($data2 as $key=>$value) {
                    $data[$key] = addslashes($value);
                }
            }
            self::$model->save($data[self::$model->getIdField()], $data);
            self::$model->afterSave($data);
        }
    }

    static function remove($id) {
        if (self::$model->isAuthorized("delete")) {
            self::$model->beforeDelete($id);
            self::$model->delete($id);
            self::$model->afterDelete($id);
        }
    }

    static function assignTo($fkAlias, $value, $id) {
        self::$model->assign($fkAlias, $value, $id);
    }

    static function executeQuery($query) {
//echo $query;
        $result = self::$driverObject->query($query);
        if (self::$driver == "mysql") {
            if (mysql_errno(self::$driverObject->getLink()) > 0) {
                echo $query . "\n" . mysql_error(self::$driverObject->getLink());
            }
        }
        return $result;
    }

    static function getInsertedId() {
        if (self::$driver == "mysql") {
            self::$lastInsertedId = mysql_insert_id(self::$driverObject->getLink());
            return mysql_insert_id(self::$driverObject->getLink());
        }
        return null;
    }
}

abstract class BaseModel implements Model {
    protected $tableName;
    protected $idField;
    protected $fieldList;
    protected $restriction = "true";
    protected $projFieldList = array();
    private $page;
    private $recPerPage;

    function applyTypes($rec) {
        $record = $rec;
        for ($i = 0; $i < count($this->fieldList); $i++) {
            $value = $record[$this->fieldList[$i]['name']];
            if ($value != "") {
                if ($this->fieldList[$i]['type'] == Model::TYPE_DATE) {
                    $c = explode(" ", $record[$this->fieldList[$i]['name']]);
                    $record[$this->fieldList[$i]['name']] = $c[0];
                } else if ($this->fieldList[$i]['type'] == Model::TYPE_JSON) {
                    $data = $record[$this->fieldList[$i]['name']];
                    $subst = array(
                        "u00e1" => "á",
                        "u00ed" => "í",
                        "u0171" => "ű",
                        "u0151" => "ő",
                        "u00fc" => "ü",
                        "u00f6" => "ö",
                        "u00fa" => "ú",
                        "u00f3" => "ó",
                        "u00e9" => "é",
                    );
                    $record[$this->fieldList[$i]['name']] = json_decode(str_replace(array_keys($subst), array_values($subst), $data), true);
                }
            }
        }
        return $record;

/*
        if (!$rec) { return $rec; }
        $record = $rec;
        foreach ($record as $field=>$value) {
            if ($this->fieldList[$field]['type'] == Model::TYPE_JSON) {
                $record[$field] = json_decode($record[$field], true);
            } else if ($this->fieldList[$field]['type'] == Model::TYPE_TEXT) {
                $record[$field] = addslashes($record[$field]);
            }
        }
        return $record;
*/
    }

    function applyTypesForResultset($rs) {
        $data = array();
        for ($i = 0; $i < count($rs); $i++) {
            $data[] = $this->applyTypes($rs[$i]);
        }
        return $data;
    }

    function __construct($tblName, $idField) {
        $this->tableName = $tblName;
        $this->idField = $idField;
    }

    function getMainTable() { return $this->tableName; }
    function getPrimaryKey() {
        $result = "";
        for ($i = 0; $i < count($this->fieldList[$i]); $i++) {
            if ($this->fieldList[$i]['pk']) {
                $result = $this->fieldList[$i]['name'];
                break;
            }
        }
        return $result;
    }

    function removeUndefinedFields($arr) {
        $result = array();
        $tmp = array();
        for ($i = 0; $i < count($this->fieldList); $i++) {
            $tmp[] = $this->fieldList[$i]['name'];
        }
        foreach ($arr as $key=>$value) {
            if (in_array($key, $tmp)) {
                $result[$key] = $value;
            }
        }
        return $result;
    }

    function setProjection($fields) {
        if (count($fields) > 0) {
            for ($i = 0; $i < count($fields); $i++) {
                $this->projFieldList[] = $this->getField($fields[$i]);
            }
        }
    }

    function getSince($date, $condition = "") {
        $fieldName = "";
        $data = array();
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['type'] == Model::TYPE_DATE_ADDITION) {
                $fieldName = $this->fieldList[$i]['name'];
                break;
            }
        }
        if ($fieldName != "") {
            $query = "SELECT * FROM " . $this->tableName . " WHERE $fieldName >= '$date'";
            if ($condition != "") { $query .= " AND $condition"; }
            $query .= " LIMIT 0, 10";
            $res = Database::executeQuery($query);
            $data = Database::getDataFromResource($res);
        }
        return $data;
    }

    function getField($name) {
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['name'] == $name) { return $this->fieldList[$i]; }
        }
    }

    function setPageData($page, $recperpage) {
        $this->page = $page;
        $this->recPerPage = $recperpage;
    }

    function getPage() {
        return $this->page;
    }

    function getRecordsPerPage() {
        return $this->recPerPage;
    }

    function loadAll($condition, $preview = false, $distinct = false) {
        $result = array();
        $joinArr = array();
        $joins = "";
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['foreign']) {
                $remotemodel = $this->fieldList[$i]['modParams']['remoteTableModel'];
                $file = "site/models/" . $remotemodel . ".php";
                $remoteclass =  $remotemodel . "Model";
                include_once($file);
                $obj = new $remoteclass();
                $remotekey = $obj->getPrimaryKey();
                $remotetbl = $obj->getMainTable();
                $joinArr[] = "JOIN $remotetbl ON " . $this->tableName . "." . $this->fieldList[$i]['name'] . "=" . $remotetbl .  "." . $remotekey;
                $this->fieldList[$i]['name'] = $this->tableName . "." . $this->fieldList[$i]['name'];
            } else if ($this->fieldList[$i]['remote']) {
                $remotemodel = $this->fieldList[$i]['modParams']['remoteTableModel'];
                $file = "site/models/" . $remotemodel . ".php";
                $remoteclass =  $remotemodel . "Model";
                include_once($file);
                $obj = new $remoteclass();
                $remotetbl = $obj->getMainTable();
                $this->fieldList[$i]['name'] = $remotetbl . "." . $this->fieldList[$i]['name'];
            } else {
                $this->fieldList[$i]['name'] = $this->tableName . "." . $this->fieldList[$i]['name'];
            }
        }
        if (count($joinArr) > 0) {
            $joins = implode(" ", $joinArr);
        }
        $query = QueryHelper::select($this->tableName, (count($this->projFieldList) > 0 ? $this->projFieldList : $this->fieldList), $condition . " AND " . $this->restriction, $joins, true);
        if ($this->page > 0) {
            $limits = Database::calculatelimit($this->page, $this->recPerPage);
            $query .= " LIMIT " . $limits[0] . ", " . $limits[1];
        }
        $res = Database::executeQuery($query);
        while ($rec = mysql_fetch_assoc($res)) {
            if ($preview) {
                $previewData = $this->getPreviewData($rec);
                if (count($previewData) > 0) {
                    foreach ($previewData as $key=>$value) {
                        $rec[$key] = $value;
                    }
                }
            }
            $rec = $this->applyTypes($rec);
            $result[] = $this->renderAlias($rec);
        }
        return $result;
    }

    function rejectChanges($id) {
        $updatefield = $this->getUpdateField();
        $fieldset = array();
        if ($updatefield != "") {
            $fieldset[$updatefield] = null;
        }
        $acceptfield = $this->getAcceptField();
        if ($acceptfield != "") {
            $fieldset[$acceptfield] = 0;
        }
        $query = QueryHelper::update($this->tableName, $fieldset, $this->getIdField() ."=$id");
        Database::executeQuery($query);
    }

    function acceptChanges($id) {
        $query = "SELECT * FROM " . $this->tableName . " WHERE " . $this->getIdField() . "=$id";
        $res = Database::executeQuery($query);
        $data = Database::getDataFromResource($res);
        $previewdata = $this->getPreviewData($data[0]);
        $previewdata[$this->getUpdateField()] = null;
        $accept = $this->getAcceptField();
        if ($accept != "") {
            $previewdata[$accept] = 1;
        } 
        $acceptdate = $this->getAcceptDateField();
        if ($acceptdate != "") {
            $previewdata[$acceptdate] = "now()";
        }
        $query = QueryHelper::update($this->tableName, $previewdata, $this->getIdField() . "=$id");
        Database::executeQuery($query);
    }

    function getPreviewData($record) {
        $fld = $this->getUpdateField();
        if (strpos($fld, ".") > 0) {
            list($tbl, $fld) = split("\.", $fld);
        }
        $data = $record[$fld];
        $subst = array(
            "u00e1" => "á",
            "u00ed" => "í",
            "u0171" => "ű",
            "u0151" => "ő",
            "u00fc" => "ü",
            "u00f6" => "ö",
            "u00fa" => "ú",
            "u00f3" => "ó",
            "u00e9" => "é",
        );
        $result = json_decode(str_replace(array_keys($subst), array_values($subst), $data), true);
        return $result;
    }

    function getCount($condition) {
        $query = QueryHelper::getCount($this->tableName, $condition . " AND " . $this->restriction);
        $res = Database::executeQuery($query);
        $rec = mysql_fetch_assoc($res);
        return $rec['c'];
    }

    function save($id, $data) {
        $result = "";
        $data = $this->modifyDatabaseInput($data);
        $data = $this->applyTypes($data);
        if ($id !== null) {
            $query = QueryHelper::update($this->tableName, $data, $this->getIdField() . "=$id");
            Database::executeQuery($query);
            $result = $id;
        } else {
            $query = "";
            if ($this->getIdField() == "") {
                $query = QueryHelper::update($this->tableName, $data, "true");
            } else {
                $query = QueryHelper::insertInto($this->tableName, $data);
            }
            Database::executeQuery($query);
            $result = Database::getInsertedId();
        }
        return $result;
    }

    function accept($id) {
        $fieldName = "";
        $acceptdateField = "";
        $data = array();
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['accept']) {
                $fieldName = $this->fieldList[$i]['name'];
            }
            if ($this->fieldList[$i]['acceptdate']) {
                $acceptdateField = $this->fieldList[$i]['name'];
            }
        }
        if ($fieldName != "") {
            $query = "UPDATE " . $this->tableName . " SET $fieldName=1 WHERE " . $this->getIdField() . "=$id";
            Database::executeQuery($query);
        }
        if ($acceptdateField != "") {
            $query = "UPDAte " . $this->tableName . " SET $acceptdateField=NOW() WHERE " . $this->getIdField() . "=$id";
            Database::executeQuery($query);
        }
        return $data;
    }

    function assign($foreignKeyAlias, $value, $id) {
        $fieldName = "";
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['foreign']) {
                if ($this->fieldList[$i]['modParams']['alias'] == $foreignKeyAlias) {
                    $fieldName = $this->fieldList[$i]['name'];
                    break;
                }
            }
        }
        if ($fieldName != "") {
            $query = "UPDATE " . $this->tableName . " SET $fieldName=$value WHERE " . $this->getIdField() . "=$id";
            Database::executeQuery($query);
        }
    }

    function reject($id) {
        $fieldName = "";
        $data = array();
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['accept']) {
                $fieldName = $this->fieldList[$i]['name'];
                break;
            }
            if ($this->fieldList[$i]['acceptdate']) {
                $acceptdateField = $this->fieldList[$i]['name'];
            }
        }
        if ($fieldName != "") {
            $query = "UPDATE " . $this->tableName . " SET $fieldName=0 WHERE " . $this->getIdField() . "=$id";
            Database::executeQuery($query);
        }
        if ($acceptdateField != "") {
            $query = "UPDAte " . $this->tableName . " SET $acceptdateField='' WHERE " . $this->getIdField() . "=$id";
            Database::executeQuery($query);
        }
        return $data;
    }

    function getUpdatefield() {
        $result = "";
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['update']) {
                $result = $this->fieldList[$i]['name'];
                break;
            }
        }
        return $result;
    }

    function load($id, $fromUpdate = false) {
        $idField = $this->getField($this->getIdField());
        $updateField = $this->getUpdateField();
        if ($idField['type'] == Model::TYPE_VARCHAR) {
            $id = "'$id'";
        }
        if ($fromUpdate && $updateField != "") {
            $query = "SELECT * FROM " . $this->tableName . " WHERE " . $this->getIdField() . "=$id";
            $res = Database::executeQuery($query);
            $acceptedData = Database::getDatafromResource($res);
            $acceptedData = $acceptedData[0];
            $query = "SELECT `$updateField` FROM ".$this->tableName." WHERE " . $this->getIdField()."=$id AND " . $this->restriction;
            $res = Database::executeQuery($query);
            $data = Database::getDatafromResource($res);
            if ($data[0][$updateField] != "") {
                $subst = array(
                    "u00e1" => "á",
                    "u00ed" => "í",
                    "u0171" => "ű",
                    "u0151" => "ő",
                    "u00fc" => "ü",
                    "u00f6" => "ö",
                    "u00fa" => "ú",
                    "u00f3" => "ó",
                    "u00e9" => "é",
                );
                $result = json_decode(str_replace(array_keys($subst), array_values($subst), $data[0][$updateField]), true);
                if (is_array($result)) {
                    foreach ($result as $key=>$value) {
                        $acceptedData[$key] = $value;
                    }
                }
                $result = $this->applyTypes($acceptedData);
                return $this->renderAlias($result);
            }
        }
        $query = QueryHelper::select($this->tableName, $this->fieldList, $this->getIdField() . "=$id AND " . $this->restriction);
        $res = Database::executeQuery($query);
        $rec = mysql_fetch_assoc($res);
        $rec = $this->applyTypes($rec);
        return $this->renderAlias($rec);
    }

    function getFieldNames() {
        $result = array();
        for ($i = 0; $i < count($this->fieldList); $i++) {
            $result[] = $this->fieldList[$i]['name'];
        }
        return $result;
    }

    function delete($id) {
        $query = QueryHelper::delete($this->tableName, $this->getIdField() . "=$id AND " . $this->restriction);
        Database::executeQuery($query);
    }

    function addField($fieldName, $type, $modifiers = 0, $modParams = array()) {
        $isPrimary = $modifiers & Model::MOD_PRIMARY;
        $isEncrypted = $modifiers & Model::MOD_MD5;
        $acceptflag = $modifiers & Model::MOD_ACCEPT;
        $acceptDate = $modifiers & Model::MOD_ACCEPTDATE;
        $foreign    = $modifiers & Model::MOD_FOREIGN;
        $remote     = $modifiers & Model::MOD_REMOTE;
        $update     = $modifiers & Model::MOD_UPDATE;

        $this->fieldList[] = array(
            name => $fieldName,
            type => $type,
            modParams => $modParams,
            pk => $isPrimary,
            md5 => $isEncrypted,
            accept => $acceptflag,
            acceptdate => $acceptDate,
            foreign => $foreign,
            remote => $remote,
            update => $update,
        );
    }
    
    function getIdField() {
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['pk']) { return $this->fieldList[$i]['name']; }
        }
        return "";
    }

    function getAcceptField() {
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['accept']) { return $this->fieldList[$i]['name']; }
        }
        return "";
    }

    function getAcceptDateField() {
        for ($i = 0; $i < count($this->fieldList); $i++) {
            if ($this->fieldList[$i]['acceptdate']) { return $this->fieldList[$i]['name']; }
        }
        return "";
    }

    function myFieldList() {
        $result = array();
        for ($i = 0; $i < count($this->fieldList); $i++) {
            $result[] = $this->fieldList[$i]['name'];
        }
        return $result;
    }

    function validate($params) {
        return array(
            overall => true
        );
    }

    function beforeSave($data) { return $data; }
    
    function afterSave($data) { return $data; }
    
    function beforeDelete($id) { return true; }
    
    function afterDelete($id) { return true; }

    abstract function renderAlias($record);
    abstract function modifyDatabaseInput($data);
}

class ModelAdapter implements Model {
    function save($id, $data) { return; }
    function load($id) { return; }
    function delete($id) { return; }
    function loadAll($condition) { return; }
    function getIdField() { return; }
    function getCount($condition) { return; }
    function isAuthorized($operation, $subject = "") { return true; }
    function beforeSave($data) { return $data; }
    function afterSave($data) { return; }
    function beforeDelete($id) { return; }
    function afterDelete($id) { return; }
    function renderAlias($record) { return $record; }
    function modifyDatabaseInput($data) { return $data; }
    function getSince($date) { return array(); }
    function accept($id) { return; }
    function reject($id) { return; }
    function validate($params) {
        return array(
            overall => true
        );
    }
}

?>
